diff --git a/group_vars/vps/sysctl.yml b/group_vars/vps/sysctl.yml index 88aea33..1ffb606 100644 --- a/group_vars/vps/sysctl.yml +++ b/group_vars/vps/sysctl.yml @@ -15,7 +15,6 @@ sysctl_params: - { key: net.ipv4.tcp_window_scaling, value: "1" } - { key: net.core.default_qdisc, value: "fq" } - { key: net.ipv4.tcp_congestion_control, value: "bbr" } - - { key: net.ipv4.tcp_no_metrics_save, value: "0" } - { key: net.ipv4.tcp_slow_start_after_idle, value: "0" } - { key: net.ipv4.tcp_fastopen, value: "3" } - { key: net.core.netdev_max_backlog, value: "16384" } diff --git a/nfs-stability.yml b/nfs-stability.yml deleted file mode 100644 index c5c3014..0000000 --- a/nfs-stability.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: NFS and mergerfs stability - hosts: emby-sascha:emby-chris:arrapps - become: yes - roles: - - nfs_stability diff --git a/pfannkuchen.ini b/pfannkuchen.ini index 943d6b7..6cd6201 100644 --- a/pfannkuchen.ini +++ b/pfannkuchen.ini @@ -40,7 +40,7 @@ n8n ansible_host=10.4.1.113 hermes ansible_host=10.4.1.100 monitoring ansible_host=10.1.1.111 automation1 ansible_host=10.5.85.5 - +outline ansible_host=10.1.1.100 satisfactory ansible_host=10.3.1.120 [communication] @@ -52,7 +52,6 @@ butler ansible_host=10.5.85.2 [hetzner] pfannkuchen ansible_host=46.225.230.72 ansible_user=root -guck-vps ansible_host=141.94.237.199 ansible_user=debian [nvidia] tdarr diff --git a/pfannkuchen.sh b/pfannkuchen.sh index 7f0955b..6e91824 100755 --- a/pfannkuchen.sh +++ b/pfannkuchen.sh @@ -23,7 +23,6 @@ usage() { echo -e " ${C}wstunnel${N} [host] wstunnel + WireGuard deployen" echo -e " ${C}sshfs${N} [host] SSHFS Mounts einrichten" echo -e " ${C}tune${N} Sysctl Netzwerk-Tuning" - echo -e " ${C}nfs${N} NFS-/mergerfs-Stabilisierung ausrollen" echo -e " ${C}pvetune${N} [host] Proxmox Host Tuning (sysctl, resolv, hosts)" echo -e " ${C}tc${N} [host] tc per-flow Rate-Limit (50 Mbit/s pro Connection)" echo -e " ${C}watchdog${N} Network Watchdog deployen (Whitelist aus Ansible)" @@ -104,10 +103,6 @@ case "$CMD" in [ -z "$HOST" ] && echo -e "${R}Fehler: Host angeben${N}" && exit 1 run sysctl.yaml -l "$HOST" ;; - nfs) - [ -z "$HOST" ] && echo -e "${R}Fehler: Host angeben${N}" && exit 1 - run nfs-stability.yml -l "$HOST" - ;; pvetune) if [ -n "$HOST" ]; then run sysctl-proxmox.yaml -l "$HOST" diff --git a/roles/nfs_stability/tasks/main.yml b/roles/nfs_stability/tasks/main.yml deleted file mode 100644 index 8073ba6..0000000 --- a/roles/nfs_stability/tasks/main.yml +++ /dev/null @@ -1,134 +0,0 @@ ---- -- 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 diff --git a/roles/sysctl/defaults/main.yml b/roles/sysctl/defaults/main.yml index 87f88e3..faa4bf4 100644 --- a/roles/sysctl/defaults/main.yml +++ b/roles/sysctl/defaults/main.yml @@ -1,5 +1,5 @@ --- -# Default-Sysctl-Werte fuer Streaming-VMs und den WireGuard-Medienpfad. +# Default-Sysctl-Werte fuer Streaming-VMs. # Pro Host/Gruppe ueberschreibbar via group_vars/host_vars (z.B. group_vars/vps). sysctl_params: - { key: net.core.rmem_default, value: "262144" } @@ -9,16 +9,13 @@ sysctl_params: - { key: net.ipv4.tcp_rmem, value: "4096 87380 67108864" } - { key: net.ipv4.tcp_wmem, value: "4096 65536 67108864" } - { key: net.ipv4.tcp_window_scaling, value: "1" } - - { key: net.core.default_qdisc, value: "fq" } - { key: net.ipv4.tcp_congestion_control, value: "bbr" } - - { key: net.ipv4.tcp_no_metrics_save, value: "0" } - { key: net.ipv4.tcp_slow_start_after_idle, value: "0" } - { key: net.ipv4.tcp_fastopen, value: "3" } - { key: net.core.netdev_max_backlog, value: "16384" } - { key: net.core.somaxconn, value: "4096" } - - { key: net.core.optmem_max, value: "2097152" } - { key: net.ipv4.tcp_notsent_lowat, value: "16384" } - - { key: net.ipv4.tcp_fin_timeout, value: "30" } + - { key: net.ipv4.tcp_fin_timeout, value: "15" } - { key: net.ipv4.tcp_tw_reuse, value: "1" } - { key: vm.swappiness, value: "1" } - { key: vm.dirty_ratio, value: "15" } diff --git a/roles/sysctl/tasks/main.yml b/roles/sysctl/tasks/main.yml index 2b95113..3563613 100644 --- a/roles/sysctl/tasks/main.yml +++ b/roles/sysctl/tasks/main.yml @@ -10,27 +10,7 @@ dest: /etc/modules-load.d/bbr.conf mode: "0644" -- name: Legacy-Sysctl-Dateien pruefen - ansible.builtin.stat: - path: "{{ item }}" - loop: - - /etc/sysctl.conf - - /etc/sysctl.d/99-streaming.conf - - /etc/sysctl.d/99-proxmox-tuning.conf - - /etc/sysctl.d/99-zzz-cloudflare-warp-connector.conf - register: legacy_sysctl_files - -- name: Verwaltete Werte aus konkurrierenden Sysctl-Quellen entfernen - ansible.builtin.lineinfile: - path: "{{ item.0.item }}" - regexp: "^[ \\t]*{{ item.1.key | regex_escape }}[ \\t]*=" - state: absent - loop: "{{ legacy_sysctl_files.results | product(sysctl_params) | list }}" - loop_control: - label: "{{ item.0.item }}: {{ item.1.key }}" - when: item.0.stat.exists - -- name: Sysctl Parameter kanonisch setzen +- name: Sysctl Parameter setzen ansible.posix.sysctl: name: "{{ item.key }}" value: "{{ item.value }}" diff --git a/roles/sysctl_proxmox/tasks/main.yml b/roles/sysctl_proxmox/tasks/main.yml index 89f046d..70bf05a 100644 --- a/roles/sysctl_proxmox/tasks/main.yml +++ b/roles/sysctl_proxmox/tasks/main.yml @@ -38,10 +38,18 @@ state: present loop: - { key: vm.overcommit_memory, value: "1" } + - { key: vm.swappiness, value: "1" } - { key: fs.file-max, value: "9999999" } - { key: fs.inotify.max_user_watches, value: "524288" } - { key: fs.inotify.max_user_instances, value: "512" } + - { key: net.ipv4.ip_forward, value: "1" } + - { key: net.ipv6.conf.all.forwarding, value: "1" } - { key: net.bridge.bridge-nf-call-iptables, value: "0" } - { key: net.bridge.bridge-nf-call-ip6tables, value: "0" } - { key: vm.dirty_expire_centisecs, value: "3000" } - { key: vm.dirty_writeback_centisecs, value: "500" } + - { key: net.ipv4.tcp_mtu_probing, value: "1" } + - { key: net.core.rmem_max, value: "67108864" } + - { key: net.core.wmem_max, value: "67108864" } + - { key: net.ipv4.tcp_rmem, value: "4096 87380 67108864" } + - { key: net.ipv4.tcp_wmem, value: "4096 65536 67108864" } diff --git a/site.yml b/site.yml index 6152040..f359ccd 100644 --- a/site.yml +++ b/site.yml @@ -1,11 +1,4 @@ --- -# Stabilize NFS/mergerfs only on hosts that use the shared media pool. -- name: NFS and mergerfs stability - hosts: emby-sascha:emby-chris:arrapps - become: yes - roles: - - nfs_stability - # Neue VM komplett einrichten - name: VM Setup hosts: all diff --git a/sysctl-proxmox.yaml b/sysctl-proxmox.yaml index ff391b8..3f9ad2d 100644 --- a/sysctl-proxmox.yaml +++ b/sysctl-proxmox.yaml @@ -3,5 +3,4 @@ hosts: proxmox become: yes roles: - - sysctl - sysctl_proxmox diff --git a/tests/test_nfs_stability_role.py b/tests/test_nfs_stability_role.py deleted file mode 100644 index 820ddfd..0000000 --- a/tests/test_nfs_stability_role.py +++ /dev/null @@ -1,27 +0,0 @@ -from pathlib import Path - -ROLE = Path("roles/nfs_stability/tasks/main.yml").read_text() -SITE = Path("site.yml").read_text() - - -def test_recovery_is_non_destructive(): - assert "systemctl restart" not in ROLE - assert "OnUnitActiveSec" not in ROLE - assert "systemctl start" in ROLE - - -def test_recovery_validates_real_nfs_and_mergerfs_mounts(): - assert "-t nfs,nfs4" in ROLE - assert "-t fuse.mergerfs" in ROLE - assert all(path in ROLE for path in ("/mnt/nas", "/mnt/qnap1", "/mnt/qnap2", "/mnt/media")) - - -def test_legacy_loop_is_stopped_before_replacement(): - stop_at = ROLE.index("Stop and disable the destructive legacy retry timer first") - install_at = ROLE.index("Install once-per-boot NFS recovery timer") - assert stop_at < install_at - - -def test_role_is_scoped_to_media_mount_hosts(): - assert "hosts: emby-sascha:emby-chris:arrapps" in SITE - assert "- nfs_stability" in SITE diff --git a/tests/test_sysctl_role.py b/tests/test_sysctl_role.py deleted file mode 100644 index b93a0f1..0000000 --- a/tests/test_sysctl_role.py +++ /dev/null @@ -1,39 +0,0 @@ -from pathlib import Path -import re -import unittest - -ROOT = Path(__file__).parents[1] - - -class SysctlRoleContract(unittest.TestCase): - def test_canonical_streaming_profile_keeps_64mib_and_bbr_fq(self): - text = (ROOT / "roles/sysctl/defaults/main.yml").read_text() - self.assertIn("net.core.rmem_max", text) - self.assertIn('value: "67108864"', text) - self.assertIn("net.core.default_qdisc", text) - self.assertIn("net.ipv4.tcp_congestion_control", text) - self.assertIn("net.ipv4.tcp_no_metrics_save", text) - - def test_role_removes_managed_keys_from_legacy_sources(self): - text = (ROOT / "roles/sysctl/tasks/main.yml").read_text() - self.assertIn("/etc/sysctl.conf", text) - self.assertIn("/etc/sysctl.d/99-streaming.conf", text) - self.assertIn("/etc/sysctl.d/99-proxmox-tuning.conf", text) - self.assertIn("/etc/sysctl.d/99-zzz-cloudflare-warp-connector.conf", text) - self.assertIn("state: absent", text) - self.assertIn("regex_escape", text) - - def test_proxmox_tuning_runs_canonical_network_role_first(self): - play = (ROOT / "sysctl-proxmox.yaml").read_text() - self.assertLess(play.index("- sysctl\n"), play.index("- sysctl_proxmox")) - - def test_proxmox_role_does_not_duplicate_canonical_network_keys(self): - canonical = (ROOT / "roles/sysctl/defaults/main.yml").read_text() - proxmox = (ROOT / "roles/sysctl_proxmox/tasks/main.yml").read_text() - canonical_keys = set(re.findall(r"key:\s*([a-z0-9_.-]+)", canonical)) - proxmox_keys = set(re.findall(r"key:\s*([a-z0-9_.-]+)", proxmox)) - self.assertEqual(canonical_keys & proxmox_keys, set()) - - -if __name__ == "__main__": - unittest.main()