--- - name: Ensure this role only targets the three mergerfs media hosts ansible.builtin.assert: that: - inventory_hostname in ['emby-sascha', 'emby-chris', 'arrapps'] fail_msg: "nfs_stability must not run on {{ inventory_hostname }}" - name: Stop and disable the destructive legacy retry timer first ansible.builtin.systemd: name: remount-nfs.timer state: stopped enabled: false failed_when: false - name: Stop a currently running destructive legacy retry service ansible.builtin.systemd: name: remount-nfs.service state: stopped failed_when: false - name: Mark mergerfs as a network-dependent mount without remounting it live ansible.builtin.replace: path: /etc/systemd/system/mnt-media.mount regexp: '^Options=(?![^\n]*_netdev)(.*)$' replace: 'Options=_netdev,\1' backup: true register: mergerfs_unit - name: Install non-destructive one-shot NFS recovery helper ansible.builtin.copy: dest: /usr/local/sbin/remount-nfs-safe owner: root group: root mode: '0755' content: | #!/bin/bash set -u is_nfs() { findmnt -rn --target "$1" -t nfs,nfs4 -o TARGET | grep -Fxq "$1" } is_media() { findmnt -rn --target /mnt/media -t fuse.mergerfs -o TARGET | grep -Fxq /mnt/media } for attempt in 1 2 3; do missing=0 for entry in 'mnt-nas.mount:/mnt/nas' 'mnt-qnap1.mount:/mnt/qnap1' 'mnt-qnap2.mount:/mnt/qnap2'; do unit="${entry%%:*}" target="${entry#*:}" if ! is_nfs "$target"; then systemctl start "$unit" || true fi is_nfs "$target" || missing=1 done if [ "$missing" -eq 0 ]; then if ! is_media; then systemctl start mnt-media.mount || true fi is_media && exit 0 fi sleep 10 done echo 'NFS recovery failed without restarting any active mount' >&2 exit 1 - name: Install safe one-shot NFS recovery service ansible.builtin.copy: dest: /etc/systemd/system/remount-nfs.service owner: root group: root mode: '0644' backup: true content: | [Unit] Description=Safely start missing NFS mounts once after boot After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/sbin/remount-nfs-safe - name: Install once-per-boot NFS recovery timer ansible.builtin.copy: dest: /etc/systemd/system/remount-nfs.timer owner: root group: root mode: '0644' backup: true content: | [Unit] Description=One-shot NFS mount validation after boot [Timer] OnBootSec=45 AccuracySec=5 Persistent=false Unit=remount-nfs.service [Install] WantedBy=timers.target - name: Reload systemd and enable the one-shot boot timer ansible.builtin.systemd: daemon_reload: true name: remount-nfs.timer enabled: true state: started - name: Run the safe helper once to recover mounts left missing by the legacy loop ansible.builtin.command: argv: - /usr/local/sbin/remount-nfs-safe changed_when: false - name: Verify systemd unit dependency graph ansible.builtin.command: argv: - systemd-analyze - verify - /etc/systemd/system/mnt-media.mount - /etc/systemd/system/remount-nfs.service - /etc/systemd/system/remount-nfs.timer changed_when: false - name: Verify all active storage mounts without restarting them ansible.builtin.shell: | set -e for target in /mnt/nas /mnt/qnap1 /mnt/qnap2; do findmnt -rn --target "$target" -t nfs,nfs4 -o TARGET | grep -Fxq "$target" done findmnt -rn --target /mnt/media -t fuse.mergerfs -o TARGET | grep -Fxq /mnt/media args: executable: /bin/bash changed_when: false