From e0a81d6a7077a1efdc896d631c102396090eedca Mon Sep 17 00:00:00 2001 From: Fabio Scotto di Santolo Date: Mon, 31 Aug 2026 21:32:03 +0200 Subject: [PATCH] Add dormant Rocky server profile --- AGENTS.md | 20 +++++- README.md | 28 ++++++-- .../inventory/group_vars/platform_rocky.yml | 32 +++++++-- ansible/inventory/group_vars/rocky_server.yml | 57 ++++++++++++++++ ansible/inventory/group_vars/ubuntu.yml | 4 ++ ansible/inventory/host_vars/atlas.yml | 10 +++ ansible/inventory/hosts.yml | 6 ++ ansible/roles/packages_rocky/tasks/main.yml | 66 +++++++++++++++++-- .../roles/profile_server/handlers/main.yml | 2 +- ansible/roles/profile_server/tasks/main.yml | 42 +++++++++++- ansible/site.yml | 8 +++ .../templates/server/docker-compose.yml.j2 | 19 +++--- 12 files changed, 263 insertions(+), 31 deletions(-) create mode 100644 ansible/inventory/group_vars/rocky_server.yml diff --git a/AGENTS.md b/AGENTS.md index db4c617..6d10e9a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,7 @@ # AGENTS.md -Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBSD transition targets, WSL, and an Ubuntu server. +Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBSD transition targets, +WSL, an Ubuntu server, and a dormant Rocky Linux 9 server profile. ## Source Of Truth - Main orchestration: `ansible/site.yml` @@ -16,6 +17,7 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBS - Void desktop profile is also the base for other future/reference hosts via `platform_void + graphical_desktop` - Workstation: `deadalus` is Windows + Fedora WSL. - Ubuntu server: `prometheus` +- Dormant Rocky server: `rocky_server` is empty until a migration target is explicitly assigned. - NAS: `atlas` (Rocky Linux 9, reached through SSH) - Hosts intentionally belong to multiple groups; trust `ansible/site.yml` over hostname assumptions. - Inventory axes are independent: `platform_*`, `role_*`, and `desktop_*`. Legacy `void` and `desktop` remain compatibility parents. @@ -41,6 +43,7 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBS - Fedora laptop work: `ansible-playbook ansible/site.yml --limit nymph --check --diff` - WSL workstation dev: `ansible-playbook ansible/site.yml --limit deadalus --check --diff` - Server: `ansible-playbook ansible/site.yml --limit prometheus --check --diff` + - Rocky server after activation: `ansible-playbook ansible/site.yml --limit --check --diff` - Atlas NAS: `ansible-playbook ansible/site.yml --limit atlas --check --diff` - Focused checks: - Emacs is disabled by default; temporary Emacs check: `ansible-playbook ansible/site.yml --limit --tags emacs --check --diff -e emacs_enabled=true` @@ -78,7 +81,9 @@ The Void desktop package lists in `ansible/inventory/group_vars/void.yml` are ke - `desktop_common_packages` — GUI infrastructure shared by the minimal desktop mode. - `desktop_minimal_packages` — applications, integration components, and the `emptty` display manager. - `desktop_sway_packages` — binaries specific to the Sway session. -`profile_packages` in the same file is cross-distro and is overridden by `group_vars/server.yml` and the workstation group vars; do not move desktop-specific Void entries through it. +`profile_packages` remains the shared package bucket for Void, Fedora, and Ubuntu profiles. Rocky uses +`rocky_profile_packages` so RPM-specific names do not leak back into the other platforms; do not move +desktop-specific Void entries through either bucket. The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-independent content and `desktop_minimal_dotfiles` carries Thunar, Udiskie, and MIME defaults. `desktop_void_dotfiles` remains reserved for files that need the Void runtime. ## Workstation Notes @@ -87,6 +92,17 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i - Fedora WSL installs Mise from the official `jdxcode/mise` COPR and uses its pinned Temurin Java 11 JDK; update the declared Mise version deliberately. - Windows applications are installed manually and are not managed from the WSL profile. +## Rocky Server Notes +- `rocky_server` is an empty child of both `platform_rocky` and `server`; it must stay empty until a + real migration target is ready. +- The active `prometheus` host remains in `ubuntu_server`. Never place the same inventory host in both + `ubuntu_server` and `rocky_server`; use a distinct name while testing a parallel replacement. +- The target must already provide `server_username` with local sudo access before the profile runs. +- The Rocky profile installs Docker CE, uses firewalld, preserves SELinux enforcement, and renders the + same server Compose stack. It does not transfer data, start containers, update DNS, or cut over traffic. +- Atlas-only OpenZFS, NFS, Samba, Cockpit, and native Syncthing packages stay selected through Atlas + host variables and must not leak into `rocky_server`. + ## Atlas NAS Notes - `atlas` is a remote Rocky Linux 9 NAS. Keep its connection, LAN, pool and mountpoint values in `host_vars/atlas.yml`. Bootstrap it once with `-e atlas_connection_username=`; diff --git a/README.md b/README.md index 570f74f..767487b 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,9 @@ infra/ ## Managed machines -The repo currently covers Fedora/GNOME desktops, one Fedora WSL workstation, an Ubuntu server, and -a Rocky Linux 9 NAS. Configuration is layered instead of being tied to host names: +The repo currently covers Fedora/GNOME desktops, one Fedora WSL workstation, an Ubuntu server, a +dormant Rocky Linux 9 server profile, and a Rocky Linux 9 NAS. Configuration is layered instead of +being tied to host names: ```text common user environment @@ -53,6 +54,7 @@ common user environment | `nymph` | Fedora | Desktop laptop | GNOME | | `deadalus` | Fedora WSL | Development workstation | — | | `prometheus` | Ubuntu | Server | — | +| — (`rocky_server`, dormant) | Rocky 9 | Server | — | | `atlas` | Rocky 9 | NAS | — | ```text @@ -99,9 +101,15 @@ That gives it Fedora packages through DNF, Docker from the official repository, ## Server -`prometheus` is the Ubuntu LTS server. It has no graphical environment and gets server-specific dotfiles and templates. +`prometheus` is the Ubuntu LTS server. It has no graphical environment and gets server-specific +dotfiles and templates. `rocky_server` is the empty alternative profile for a future Rocky Linux 9 +migration; it does not select any host until one is explicitly added to that inventory group. -The server profile installs Ubuntu packages, Docker from the official repository, declared systemd services, UFW rules, and the server Compose stack. Syncthing ports `22000/tcp`, `22000/udp`, and `21027/udp` are opened; the Syncthing GUI is not directly opened in UFW. +The server profile installs platform-specific packages, Docker CE from the official repository, +declared systemd services, the server Compose stack, and either UFW on Ubuntu or firewalld on Rocky. +Syncthing ports `22000/tcp`, `22000/udp`, and `21027/udp` are opened; the Syncthing GUI is not +directly exposed by the managed firewall rules. Rocky bind mounts use private SELinux relabeling for +application data while host system files remain unchanged. Server identity comes from `server_username`, `server_user_group`, and `server_user_home` in `ansible/inventory/group_vars/server.yml`. `server_username` defaults to `username`, but it can be overridden, for example: @@ -112,6 +120,12 @@ ansible-playbook ansible/site.yml --limit prometheus \ -e server_user_home=/srv/myuser ``` +The dormant profile provisions configuration only: it does not transfer data, start the Compose +stack, update DNS, or perform a cutover. During migration, add the replacement machine to +`rocky_server` under a distinct inventory name after creating `server_username` with local sudo +access. When reusing `prometheus` at cutover, remove it from `ubuntu_server` before adding it to +`rocky_server`; a host must never belong to both platform groups. + ## NAS `atlas` is a Rocky Linux 9 NAS reached through SSH. Its pool already exists: the profile only @@ -229,13 +243,14 @@ ansible-playbook ansible/site.yml --limit deadalus --tags ai_agents --check --di ## What `site.yml` runs ```text -all -> dotfiles_common +all except platform_rocky -> dotfiles_common platform_void -> packages_void + services_runit platform_void & graphical_desktop -> profile_desktop_common + profile_desktop_sway + profile_desktop_niri + profile_desktop_host platform_freebsd -> packages_freebsd + services_freebsd platform_fedora -> packages_fedora + services_systemd platform_rocky -> packages_rocky + services_systemd atlas -> profile_atlas +rocky_server -> dotfiles_common + profile_server (after platform_rocky) platform_fedora & role_personal_workstation -> profile_personal_workstation platform_fedora & desktop_gnome -> profile_desktop_gnome workstation_dev_fedora -> profile_workstation_dev_common @@ -248,8 +263,9 @@ So, in practice: - `platform_fedora` configures `ikaros`, `nymph`, and `deadalus`. - `deadalus` gets the Fedora development layer followed by the WSL layer. - `ubuntu_server` configures `prometheus`. +- Empty `rocky_server` defines the Rocky 9 server alternative without targeting a machine. - `atlas` receives the Rocky platform layer and the NAS profile through SSH. -- Empty `platform_void` and `platform_freebsd` groups do nothing until they get a host. +- Empty `platform_void`, `platform_freebsd`, and `rocky_server` groups do nothing until they get a host. - The playbook never restarts the display manager during a run. - `secrets/vault.yml` and then `secrets/vault.local.yml` are loaded only when present. diff --git a/ansible/inventory/group_vars/platform_rocky.yml b/ansible/inventory/group_vars/platform_rocky.yml index 3ea84da..49ef00a 100644 --- a/ansible/inventory/group_vars/platform_rocky.yml +++ b/ansible/inventory/group_vars/platform_rocky.yml @@ -16,17 +16,39 @@ rocky_syncthing_archive_url: >- ~ '.tar.gz' }} +rocky_manage_openzfs_repo: false +rocky_manage_syncthing_binary: false +rocky_manage_docker_repo: false +rocky_manage_github_cli_repo: false + +rocky_docker_repo_url: https://download.docker.com/linux/rhel/docker-ce.repo +rocky_docker_repo_file: /etc/yum.repos.d/docker-ce.repo +rocky_github_cli_repo_url: https://cli.github.com/packages/rpm/gh-cli.repo +rocky_github_cli_repo_file: /etc/yum.repos.d/gh-cli.repo + enabled_services: - firewalld +rocky_common_packages: + - bash-completion + - bat + - fzf + - git + - jq + - tree + - unzip + - vim-enhanced + - wget + - zip + - zoxide + rocky_packages_base: - - cockpit - dnf-plugins-core - firewalld - - nfs-utils - openssh-server - - policycoreutils-python-utils - rsync - - samba - sudo - - zfs + +rocky_profile_packages: [] +rocky_docker_packages: [] +rocky_docker_conflicting_packages: [] diff --git a/ansible/inventory/group_vars/rocky_server.yml b/ansible/inventory/group_vars/rocky_server.yml new file mode 100644 index 0000000..bc4c73f --- /dev/null +++ b/ansible/inventory/group_vars/rocky_server.yml @@ -0,0 +1,57 @@ +--- +rocky_manage_docker_repo: true +rocky_manage_github_cli_repo: true + +rocky_profile_packages: + - avahi + - curl + - dmidecode + - dosfstools + - gcc + - gcc-c++ + - git-delta + - gh + - gnupg2 + - htop + - make + - nmap-ncat + - nodejs + - parted + - pciutils + - pinentry + - ranger + - ripgrep + +rocky_docker_packages: + - containerd.io + - docker-buildx-plugin + - docker-ce + - docker-ce-cli + - docker-compose-plugin + +rocky_docker_conflicting_packages: + - docker + - docker-client + - docker-client-latest + - docker-common + - docker-engine + - docker-latest + - docker-latest-logrotate + - docker-logrotate + - podman + - runc + +enabled_services: + - firewalld + - docker + +server_firewall_backend: firewalld +server_firewalld_zone: public +server_firewalld_services: + - ssh +server_firewalld_ports: + - 22000/tcp + - 22000/udp + - 21027/udp +server_sshd_service_name: sshd +server_compose_selinux_mount_option: Z diff --git a/ansible/inventory/group_vars/ubuntu.yml b/ansible/inventory/group_vars/ubuntu.yml index 56877ca..f70d0b3 100644 --- a/ansible/inventory/group_vars/ubuntu.yml +++ b/ansible/inventory/group_vars/ubuntu.yml @@ -31,6 +31,10 @@ enabled_services: - ufw - docker +server_firewall_backend: ufw +server_sshd_service_name: ssh +server_compose_selinux_mount_option: "" + ubuntu_dotfiles: - src: .bashrc.d/ dest: .bashrc.d/ diff --git a/ansible/inventory/host_vars/atlas.yml b/ansible/inventory/host_vars/atlas.yml index 7e6caec..6f3c86d 100644 --- a/ansible/inventory/host_vars/atlas.yml +++ b/ansible/inventory/host_vars/atlas.yml @@ -26,6 +26,16 @@ atlas_zfs_dataset_icloud_photos: icloud_photos atlas_mount_root: /CHANGEME_ATLAS_MOUNT_ROOT atlas_manage_storage: false +rocky_manage_openzfs_repo: true +rocky_manage_syncthing_binary: true + +host_packages: + - cockpit + - nfs-utils + - policycoreutils-python-utils + - samba + - zfs + atlas_nfs_exports: - path: "{{ atlas_work_mountpoint }}" clients: "{{ atlas_lan_subnet }}(rw,sync,no_subtree_check,root_squash)" diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml index e933605..5d0d2bb 100644 --- a/ansible/inventory/hosts.yml +++ b/ansible/inventory/hosts.yml @@ -19,6 +19,8 @@ all: platform_rocky: hosts: atlas: + children: + rocky_server: void: children: @@ -76,6 +78,7 @@ all: server: children: + rocky_server: ubuntu_server: workstation_dev_fedora: @@ -92,3 +95,6 @@ all: hosts: prometheus: ansible_connection: local + + rocky_server: + hosts: {} diff --git a/ansible/roles/packages_rocky/tasks/main.yml b/ansible/roles/packages_rocky/tasks/main.yml index b6a456c..4bfa28d 100644 --- a/ansible/roles/packages_rocky/tasks/main.yml +++ b/ansible/roles/packages_rocky/tasks/main.yml @@ -32,6 +32,7 @@ ansible.builtin.dnf: name: "{{ rocky_openzfs_release_rpm }}" state: present + when: rocky_manage_openzfs_repo | bool - name: Disable OpenZFS DKMS repository tags: [packages, storage] @@ -39,6 +40,7 @@ name: - zfs state: disabled + when: rocky_manage_openzfs_repo | bool - name: Enable OpenZFS kmod repository tags: [packages, storage] @@ -46,6 +48,36 @@ name: - zfs-kmod state: enabled + when: rocky_manage_openzfs_repo | bool + +- name: Configure official Docker RPM repository + tags: [packages, docker] + ansible.builtin.get_url: + url: "{{ rocky_docker_repo_url }}" + dest: "{{ rocky_docker_repo_file }}" + owner: root + group: root + mode: "0644" + when: rocky_manage_docker_repo | bool + +- name: Configure official GitHub CLI RPM repository + tags: [packages] + ansible.builtin.get_url: + url: "{{ rocky_github_cli_repo_url }}" + dest: "{{ rocky_github_cli_repo_file }}" + owner: root + group: root + mode: "0644" + when: rocky_manage_github_cli_repo | bool + +- name: Remove packages conflicting with Docker CE + tags: [packages, docker] + ansible.builtin.dnf: + name: "{{ rocky_docker_conflicting_packages }}" + state: absent + when: + - rocky_manage_docker_repo | bool + - rocky_docker_conflicting_packages | length > 0 - name: Refresh Rocky package metadata tags: [packages] @@ -58,20 +90,30 @@ name: >- {{ ( - (common_packages | default([])) + (rocky_common_packages | default([])) + (rocky_packages_base | default([])) - + (profile_packages | default([])) + + (rocky_profile_packages | default([])) + + (rocky_docker_packages | default([])) + (host_packages | default([])) ) | unique }} state: present +- name: Add Rocky user to docker group + tags: [packages, docker] + ansible.builtin.user: + name: "{{ effective_username }}" + groups: docker + append: true + when: rocky_docker_packages | length > 0 + - name: Require supported architecture for Syncthing tags: [packages, syncthing] ansible.builtin.assert: that: - ansible_facts['architecture'] == 'x86_64' fail_msg: The pinned Atlas Syncthing binary currently supports x86_64 only. + when: rocky_manage_syncthing_binary | bool - name: Read installed Syncthing version tags: [packages, syncthing] @@ -83,6 +125,7 @@ changed_when: false failed_when: false check_mode: false + when: rocky_manage_syncthing_binary | bool - name: Determine whether Syncthing must be installed tags: [packages, syncthing] @@ -92,6 +135,7 @@ ('syncthing v' ~ rocky_syncthing_version ~ ' ') not in (rocky_syncthing_version_check.stdout | default('')) }} + when: rocky_manage_syncthing_binary | bool - name: Create temporary Syncthing extraction directory tags: [packages, syncthing] @@ -101,7 +145,9 @@ owner: root group: root mode: "0755" - when: rocky_syncthing_install_required | bool + when: + - rocky_manage_syncthing_binary | bool + - rocky_syncthing_install_required | bool - name: Download pinned Syncthing release tags: [packages, syncthing] @@ -112,7 +158,9 @@ owner: root group: root mode: "0644" - when: rocky_syncthing_install_required | bool + when: + - rocky_manage_syncthing_binary | bool + - rocky_syncthing_install_required | bool - name: Extract pinned Syncthing release tags: [packages, syncthing] @@ -120,7 +168,9 @@ src: "/tmp/syncthing-{{ rocky_syncthing_version }}.tar.gz" dest: "/tmp/syncthing-{{ rocky_syncthing_version }}" remote_src: true - when: rocky_syncthing_install_required | bool + when: + - rocky_manage_syncthing_binary | bool + - rocky_syncthing_install_required | bool - name: Install pinned Syncthing binary tags: [packages, syncthing] @@ -136,16 +186,20 @@ owner: root group: root mode: "0755" - when: rocky_syncthing_install_required | bool + when: + - rocky_manage_syncthing_binary | bool + - rocky_syncthing_install_required | bool - name: Remove Syncthing release archive tags: [packages, syncthing] ansible.builtin.file: path: "/tmp/syncthing-{{ rocky_syncthing_version }}.tar.gz" state: absent + when: rocky_manage_syncthing_binary | bool - name: Remove Syncthing extraction directory tags: [packages, syncthing] ansible.builtin.file: path: "/tmp/syncthing-{{ rocky_syncthing_version }}" state: absent + when: rocky_manage_syncthing_binary | bool diff --git a/ansible/roles/profile_server/handlers/main.yml b/ansible/roles/profile_server/handlers/main.yml index ab5516d..dd7e309 100644 --- a/ansible/roles/profile_server/handlers/main.yml +++ b/ansible/roles/profile_server/handlers/main.yml @@ -2,5 +2,5 @@ - name: Reload SSH service tags: [services] ansible.builtin.service: - name: ssh + name: "{{ server_sshd_service_name }}" state: reloaded diff --git a/ansible/roles/profile_server/tasks/main.yml b/ansible/roles/profile_server/tasks/main.yml index 8cb36e2..f207a7c 100644 --- a/ansible/roles/profile_server/tasks/main.yml +++ b/ansible/roles/profile_server/tasks/main.yml @@ -1,5 +1,13 @@ --- +- name: Require supported server firewall backend + tags: [services, packages] + ansible.builtin.assert: + that: + - server_firewall_backend in ['ufw', 'firewalld'] + fail_msg: >- + server_firewall_backend must be either ufw or firewalld for the server profile. + - name: Copy server dotfiles tags: [dotfiles, dotfiles:server] ansible.builtin.copy: @@ -72,6 +80,7 @@ tags: [services, packages] ansible.builtin.set_fact: server_ufw_rules_effective: "{{ server_ufw_rules | default([]) }}" + when: server_firewall_backend == 'ufw' - name: Apply server UFW rules tags: [services, packages] @@ -89,12 +98,41 @@ interface_out: "{{ item.interface_out | default(omit) }}" route: "{{ item.route | default(omit) }}" comment: "{{ item.comment | default(omit) }}" - loop: "{{ server_ufw_rules_effective }}" + loop: "{{ server_ufw_rules_effective | default([]) }}" loop_control: label: "{{ item.name | default(item.port) }}" + when: server_firewall_backend == 'ufw' - name: Enable UFW firewall on server tags: [services, packages] community.general.ufw: state: enabled - when: (server_ufw_rules_effective | default([])) | length > 0 + when: + - server_firewall_backend == 'ufw' + - (server_ufw_rules_effective | default([])) | length > 0 + +- name: Allow server services through firewalld + tags: [services, packages] + ansible.posix.firewalld: + service: "{{ item }}" + zone: "{{ server_firewalld_zone }}" + state: enabled + permanent: true + immediate: true + loop: "{{ server_firewalld_services | default([]) }}" + loop_control: + label: "{{ item }}" + when: server_firewall_backend == 'firewalld' + +- name: Allow server ports through firewalld + tags: [services, packages] + ansible.posix.firewalld: + port: "{{ item }}" + zone: "{{ server_firewalld_zone }}" + state: enabled + permanent: true + immediate: true + loop: "{{ server_firewalld_ports | default([]) }}" + loop_control: + label: "{{ item }}" + when: server_firewall_backend == 'firewalld' diff --git a/ansible/site.yml b/ansible/site.yml index 267b0e2..3c76d69 100644 --- a/ansible/site.yml +++ b/ansible/site.yml @@ -84,6 +84,14 @@ roles: - profile_atlas +- name: Configure Rocky Linux server + hosts: rocky_server + become: true + + roles: + - dotfiles_common + - profile_server + - name: Configure personal workstation role on Fedora hosts: platform_fedora:&role_personal_workstation become: true diff --git a/ansible/templates/server/docker-compose.yml.j2 b/ansible/templates/server/docker-compose.yml.j2 index f75cda7..27c394e 100644 --- a/ansible/templates/server/docker-compose.yml.j2 +++ b/ansible/templates/server/docker-compose.yml.j2 @@ -1,4 +1,5 @@ --- +{%- set selinux_volume_option = server_compose_selinux_mount_option | default('') %} version: "3.8" services: @@ -14,8 +15,8 @@ services: ND_ENABLETRANSCODING: "true" volumes: - - "/opt/navidrome/data:/data" - - "/opt/music:/music:ro" + - "/opt/navidrome/data:/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}" + - "/opt/music:/music:ro{{ ',' ~ selinux_volume_option if selinux_volume_option else '' }}" user: "1000:1000" networks: - web @@ -30,8 +31,8 @@ services: - "443:443" - "81:81" volumes: - - "/opt/npm/data:/data" - - "/opt/npm/letsencrypt:/etc/letsencrypt" + - "/opt/npm/data:/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}" + - "/opt/npm/letsencrypt:/etc/letsencrypt{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}" networks: - web - gitea @@ -66,7 +67,7 @@ services: POSTGRES_PASSWORD: "{{ vault_postgres_root_password }}" volumes: - - "/opt/postgres/data:/var/lib/postgresql/data" + - "/opt/postgres/data:/var/lib/postgresql/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}" networks: - web gitea: @@ -79,10 +80,10 @@ services: networks: - gitea volumes: - - /opt/gitea/data:/data + - /opt/gitea/data:/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }} - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro - - /home/git/.ssh:/data/git/.ssh + - /home/git/.ssh:/data/git/.ssh{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }} ports: - "3000:3000" - "127.0.0.1:222:22" @@ -95,8 +96,8 @@ services: expose: - "8384" volumes: - - "/opt/syncthing/config:/var/syncthing" - - "/srv/syncthing/data:/data" + - "/opt/syncthing/config:/var/syncthing{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}" + - "/srv/syncthing/data:/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}" ports: - "22000:22000/tcp" - "22000:22000/udp"