From 7a33a9628cb9a3730af185f5bc00a5d8ff1b78a6 Mon Sep 17 00:00:00 2001 From: sascha Date: Fri, 14 Aug 2026 06:47:52 +0200 Subject: [PATCH] =?UTF-8?q?NFS-Stabilisierung:=20roles/nfs=5Fstability/tas?= =?UTF-8?q?ks/main.yml=20hinzuf=C3=BCgen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- roles/nfs_stability/tasks/main.yml | 128 +++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 roles/nfs_stability/tasks/main.yml diff --git a/roles/nfs_stability/tasks/main.yml b/roles/nfs_stability/tasks/main.yml new file mode 100644 index 0000000..f8b86cc --- /dev/null +++ b/roles/nfs_stability/tasks/main.yml @@ -0,0 +1,128 @@ +--- +- 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: 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